package com.foodev.maven.gwt;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;

/**
 * @goal  shell
 * @requiresDependencyResolution test
 *
 * @author Martin Algesten
 */
public class ShellMojo extends AbstractGwtScriptMojo {
  
  
  /**
   * The urls
   * 
   * @required
   * @parameter
   */
  private List<String> urls;
  

  /**
   * Runs an embedded Tomcat instance on the specified port (defaults to 8888)
   * 
   * @parameter
   */
  private int port = -1;
  
  
  /**
   * Prevents the embedded Tomcat server from running, even if a port is specified
   * 
   * @parameter
   */
  private boolean noserver = false;
  

  /**
   * The 'style' parameter, should be one of <code>OBF[USCATED]</code>,
   * <code>PRETTY</code> or <code>DETAILED</code>.
   * (Defaults to <code>OBF</code>).
   * 
   * @parameter
   */
  private String style = null;
  

  /**
   * The maximum amount of heap space to use, default is '256m' (-Xmx256m).
   *
   * @parameter expression="256m"
   */
  private String maxmem = null;


  public void execute() throws MojoExecutionException,
                               MojoFailureException {

    try {

      extractNativeLibs();
      
      File file = createScriptFile("shell");
      FileWriter fwriter = new FileWriter(file);
      PrintWriter writer = new PrintWriter(fwriter);
      
      // shell should keep the 'target/classes'
      List<String> classpath = filterClasspath( buildClasspath(), false );
     
      startScript( writer );

      writeClasspath( writer, classpath );

      writeCommand( writer, urls );
      
      writer.close();
      fwriter.close();
      
      int exitValue = 
        executeProcess(gwtHome, new String[]{file.getAbsolutePath()});
      
      if ( exitValue != 0 )
        throw new MojoExecutionException( "GWT compilation failed." +
        		" Exit value: "+exitValue );

    } catch ( MojoExecutionException e ) {
      
      throw e;

    } catch ( Exception e ) {

      getLog().error(e);

      throw new MojoFailureException( "GWT compilation failed." );
      
    }
      
  }
  
 
  private void writeCommand( PrintWriter writer, List<String> urls )
    throws IOException {
    
    writer.write( "java" );

    if ( isMac() ) {
      writer.write( " -XstartOnFirstThread");
    }
    
    if ( maxmem != null ) {
      writer.write( " -Xmx"+maxmem );
    }

    if ( isWindows() ) {
      writer.write( " -cp %CLASSPATH%" );
    }
    
    writer.write( " com.google.gwt.dev.GWTShell" );

    if ( port != -1 ) {
      writer.write( " -port "+port );
    }
    
    if ( noserver ) {
      writer.write( " -noserver" );
    }
    
    if ( getLog().isDebugEnabled() ) {
      writer.write( " -logLevel DEBUG" );
    }

    if ( style != null ) {
      writer.write( " -style "+style );
    }
    
    for ( String url : urls ) {
      writer.write( " "+url );
    }
    
    writer.println();
    
  }
  
  
  /**
   * Extracts the native libs from the .zip found on the classpath.
   * 
   * @throws MojoExecutionException
   */
  private void extractNativeLibs() throws MojoExecutionException, IOException {

    URL url = findNativeZip();
   
    if ( url == null ) 
      throw new MojoExecutionException("No classloader is of type URLClassLoader");

    if ( !"file".equals( url.getProtocol() ) ) {
      throw new MojoExecutionException( "Native library zip is not a " +
          "file URL: "+url.toString() );
    }
    
    File dir = new File(url.getPath()).getParentFile();
    
    ZipInputStream zip = new ZipInputStream(url.openStream());
    ZipEntry entry;
    byte[] buf = new byte[1024];
    int read;
    
    while( (entry = zip.getNextEntry()) != null ) {
      
      File outFile = new File( dir, entry.getName() );
      
      if ( outFile.exists() ) outFile.delete();
      
      FileOutputStream fos = new FileOutputStream(outFile);
      
      while( (read = zip.read(buf)) != -1 ) fos.write(buf, 0, read);

      outFile.setLastModified(entry.getTime());
      
      fos.close();
      
    }
    
  }
  

  /**
   * Finds the native zip on the classpath, first looks at the thread
   * context classloader, then the class classloader.
   *
   * @return the URL for the found zip or null if not found.
   * @throws MojoExecutionException
   */
  private URL findNativeZip() {

    URL url = null;
    ClassLoader cl = null;
    
    cl = Thread.currentThread().getContextClassLoader();
    if ( !(cl instanceof URLClassLoader) ) {
      getLog().info("Context ClassLoader is not a URLClassLoader");
    } else {
      url = findZip((URLClassLoader)cl);
    }
 
    if ( url != null ) return url;
    
    cl = getClass().getClassLoader();
    if ( !(cl instanceof URLClassLoader) ) {
      getLog().info("Local ClassLoader is not a URLClassLoader");
    } else {
      url = findZip((URLClassLoader)cl);
    }
    
    if ( url != null ) return url;

    return null;
    
  }
  
  
  
  
  /**
   * Goes through the URLs in a URLClassLoader and find the 
   * entry matching the name in ZIPREGEX.
   * @param cl the classloader to inspect.
   * @return the found .zip or null.
   */
  private URL findZip( URLClassLoader cl ) {
    
    URL[] urls = cl.getURLs();
    
    for( int i = 0; i < urls.length; i++ ) {

      if ( isNativeLibZip(urls[i].toString()) ) {

        return urls[i];
        
      }
      
    }
    
    return null;
    
  }

  
}
